﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using Informant.Contacts;
using Informant.ErrorHandling;
using Informant.Google.ErrorHandling;
using Informant.Google.Voice.SmsMessaging.Parsing;
using Informant.Net;
using Informant.SmsMessaging;

namespace Informant.Google.Voice
{
	public class GoogleVoiceClient : IGoogleVoiceClient
	{
		#region Fields

		private String _authToken = null;
		private String _sessionToken = null;
		private CookieWebClient _webClient = null;
		private IErrorHandler _errorHandler = null;

		#endregion

		/// <summary>
		/// Gets a value indicating whether this instance is logged on.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance is logged on; otherwise, <c>false</c>.
		/// </value>
		public Boolean LoggedOn
		{
			get
			{
				return !String.IsNullOrEmpty(_sessionToken) && !String.IsNullOrEmpty(_authToken);
			}
		}

		/// <summary>
		/// Occurs when send SMS progress updated.
		/// </summary>
		public event EventHandler<SendSmsProgressEventArgs> SendSmsProgressUpdated;
		private void OnSendSmsProgressUpdated(Int32 smsSent, Int32 smsFailed, Int32 smsTotal)
		{
			if (SendSmsProgressUpdated != null)
			{
				SendSmsProgressUpdated(this, new SendSmsProgressEventArgs(smsSent, smsFailed, smsTotal));
			}
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="GoogleVoiceClient"/> class.
		/// </summary>
		public GoogleVoiceClient()
		{
			_webClient = new CookieWebClient();
			_errorHandler = new ErrorHandler();
		}

		#region Public Members

		/// <summary>
		/// Logs on the specified user.
		/// </summary>
		/// <param name="username">The username. (ex. "name@gmail.com" or just "name")</param>
		/// <param name="password">The password.</param>
		/// <returns></returns>
		/// <exception cref="System.Security.Authentication.AuthenticationException"/>
		/// <exception cref="System.Net.WebException"/>
		public Boolean LogOn(String username, String password)
		{
			_sessionToken = RetrieveSessionToken(username, password);
			_authToken = RetrieveAuthToken(username, password);

			if (!LoggedOn)
			{
				throw new System.Security.Authentication.AuthenticationException(
					ErrorResources.AuthenticationExceptionInvalidCredentialsOrTargetServerAddress);
			}
			return LoggedOn;
		}
		/// <summary>
		/// Logs off the current user.
		/// </summary>
		/// <returns></returns>
		/// <exception cref="System.Security.Authentication.AuthenticationException"/>
		/// <exception cref="System.Net.WebException"/>
		public Boolean LogOff()
		{
			Boolean loggedOut = ServerLogout();
			if (loggedOut)
			{
				_authToken = null;
				_sessionToken = null;
				_webClient = null;
			}
			return loggedOut;
		}
		/// <summary>
		/// Deletes the Sms.
		/// </summary>
		/// <param name="smsId">The Sms id.</param>
		/// <returns>True if successful.</returns>
		/// <exception cref="System.Security.Authentication.AuthenticationException"/>
		/// <exception cref="System.Net.WebException"/>
		public Boolean DeleteSms(String smsId)
		{
			return DeleteSmsFromServer(smsId);
		}
		/// <summary>
		/// Marks the target Sms as read.
		/// </summary>
		/// <param name="smsId">The Sms id.</param>
		/// <param name="isRead">if set to <c>true</c> [is read].</param>
		/// <returns>True if successful.</returns>
		/// <exception cref="System.Security.Authentication.AuthenticationException"/>
		/// <exception cref="System.Net.WebException"/>
		public Boolean MarkReadSms(String smsId, Boolean isRead)
		{
			return MarkReadSmsOnServer(smsId, isRead);
		}
		/// <summary>
		/// Retrives all Sms.
		/// </summary>
		/// <returns></returns>
		/// <exception cref="System.Security.Authentication.AuthenticationException"/>
		/// <exception cref="System.Net.WebException"/>
		public IEnumerable<ISms> RetrieveAllSms()
		{
			ISmsXmlParser xmlParser = new SmsXmlParser();
			return xmlParser.ParseToSms(RetrieveAllSmsXml());
		}
		/// <summary>
		/// Sends an Sms to a single recipient.
		/// </summary>
		/// <param name="sms"></param>
		/// <returns>
		/// Enumeration of all Phone that sms send failed for.
		/// </returns>
		/// <exception cref="System.Security.Authentication.AuthenticationException"/>
		/// <exception cref="System.Net.WebException"/>
		/// <exception cref="Informant.ErrorHandling.SmsException"/>
		public Boolean SendSms(ISms sms)
		{
			Boolean result;
			Int32 smsSent = 0;
			List<IPhone> rejectedRecipients = new List<IPhone>();

			foreach (IPhone phone in sms.Recipients)
			{
				//Don't sleep for the first text.
				if (smsSent + rejectedRecipients.Count != 0)
				{
					Thread.Sleep(2000);
				}

				if (!SendSmsOnServer(sms.Messages.First(), phone))
				{
					rejectedRecipients.Add(phone);
				}
				else
				{
					smsSent++;
				}

				OnSendSmsProgressUpdated(smsSent, rejectedRecipients.Count, sms.Recipients.Count());
			}

			result = rejectedRecipients.Count == 0;

			if (!result)
			{
				throw new SmsException("Sending to one or more recipients failed.", rejectedRecipients);
			}
			return result;
		}

		#endregion

		#region Private Methods

		/// <summary>
		/// Retrieves the GALX.
		/// </summary>
		/// <returns></returns>
		/// <exception cref="System.Net.WebException"/>
		private String RetrieveGALX()
		{
			String result = null;
			try
			{
				result = _webClient.SimpleGet(URLResources.GALX_URL).FindMatchWithRegex(RegexResources.GALX, 1);
			}
			catch (WebException ex)
			{
				_errorHandler.MakeExceptionClientFriendly(ex);
			}
			return result;
		}
		/// <summary>
		/// Retrieves the auth token.
		/// </summary>
		/// <param name="username">The username.</param>
		/// <param name="password">The password.</param>
		/// <returns></returns>
		/// <exception cref="System.Security.Authentication.AuthenticationException"/>
		/// <exception cref="System.Net.WebException"/>
		private String RetrieveAuthToken(String username, String password)
		{
			String requestAuthenticationURL = String.Format(URLResources.AUTHENTICATE_URL, QualifyUsername(username), password);
			String result = null;
			try
			{
				result = _webClient.SimplePost(requestAuthenticationURL).FindMatchWithRegex(RegexResources.Auth, 1);
			}
			catch (WebException ex)
			{
				_errorHandler.MakeExceptionClientFriendly(ex);
			}
			return result;
		}
		/// <summary>
		/// Retrieves the session token.
		/// </summary>
		/// <param name="username">The username.</param>
		/// <param name="password">The password.</param>
		/// <returns></returns>
		/// <exception cref="System.Security.Authentication.AuthenticationException"/>
		/// <exception cref="System.Net.WebException"/>
		private String RetrieveSessionToken(String username, String password)
		{
			String requestSessionURL = String.Format(URLResources.LOGIN_URL, QualifyUsername(username), password, RetrieveGALX());
			String result = null;
			try
			{
				result = _webClient.SimplePost(requestSessionURL).FindMatchWithRegex(RegexResources.Session, 2);
			}
			catch (WebException ex)
			{
				_errorHandler.MakeExceptionClientFriendly(ex);
			}
			return result;
		}
		/// <summary>
		/// Logs out the current user.
		/// </summary>
		/// <returns></returns>
		/// <exception cref="System.Net.WebException"/>
		private Boolean ServerLogout()
		{
			Boolean succeeded = false;
			try
			{
				String result = _webClient.SimpleGet(URLResources.LOGOUT_URL);
				succeeded = true;
			}
			catch (WebException ex)
			{
				_errorHandler.MakeExceptionClientFriendly(ex);
			}
			return succeeded;
		}
		/// <summary>
		/// Qualifies the username.
		/// </summary>
		/// <param name="username">The username.</param>
		/// <returns></returns>
		private String QualifyUsername(String username)
		{
			String qualifiedUsername = username;
			String gmail = "@gmail.com";
			if (!qualifiedUsername.Contains(gmail))
			{
				qualifiedUsername = String.Concat(qualifiedUsername, gmail);
			}
			return qualifiedUsername;
		}
		/// <summary>
		/// Deletes the Sms from server.
		/// </summary>
		/// <param name="smsId">The Sms id.</param>
		/// <returns></returns>
		/// <exception cref="System.Security.Authentication.AuthenticationException"/>
		/// <exception cref="System.Net.WebException"/>
		private Boolean DeleteSmsFromServer(String smsId)
		{
			Boolean succeeded = false;
			try
			{
				String result = _webClient.SimplePost(String.Format(URLResources.DELETE_Sms_URL, smsId, _sessionToken));
				Boolean.TryParse(result.FindMatchWithRegex(RegexResources.ResultOK, 1), out succeeded);
			}
			catch (WebException ex)
			{
				_errorHandler.MakeExceptionClientFriendly(ex);
			}
			return succeeded;
		}
		/// <summary>
		/// Marks the Sms read.
		/// </summary>
		/// <param name="smsId">The Sms id.</param>
		/// <returns></returns>
		/// <exception cref="System.Security.Authentication.AuthenticationException"/>
		/// <exception cref="System.Net.WebException"/>
		private Boolean MarkReadSmsOnServer(String smsId, Boolean isRead)
		{
			Boolean succeeded = false;
			try
			{
				String result = _webClient.SimplePost(String.Format(URLResources.TOGGLE_Sms_IS_READ_URL, smsId, isRead ? 1 : 0, _sessionToken));
				Boolean.TryParse(result.FindMatchWithRegex(RegexResources.ResultOK, 1), out succeeded);
			}
			catch (WebException ex)
			{
				_errorHandler.MakeExceptionClientFriendly(ex);
			}
			return succeeded;
		}
		/// <summary>
		/// Retrieves all Sms in XML format.
		/// </summary>
		/// <returns></returns>
		/// <exception cref="System.Security.Authentication.AuthenticationException"/>
		/// <exception cref="System.Net.WebException"/>
		private String RetrieveAllSmsXml()
		{
			String result = null;
			try
			{
				return _webClient.SimpleGet(String.Format(URLResources.RETRIEVE_ALL_Sms_URL, _authToken));
			}
			catch (WebException ex)
			{
				_errorHandler.MakeExceptionClientFriendly(ex);
			}
			return result;
		}
		/// <summary>
		/// Sends the Sms.
		/// </summary>
		/// <param name="message">The message.</param>
		/// <param name="phone">The phone.</param>
		/// <returns></returns>
		/// <exception cref="System.Security.Authentication.AuthenticationException"/>
		/// <exception cref="System.Net.WebException"/>
		private Boolean SendSmsOnServer(IMessage message, IPhone phone)
		{
			Boolean succeeded = false;
			try
			{
				String result = _webClient.SimplePost(String.Format(URLResources.SEND_Sms_URL, phone.Number, message.Text, _sessionToken));
				Boolean.TryParse(result.FindMatchWithRegex(RegexResources.ResultOK, 1), out succeeded);
			}
			catch (WebException ex)
			{
				_errorHandler.MakeExceptionClientFriendly(ex);
			}
			return succeeded;
		}

		#endregion
	}
}
